#include <pthread.h>
#include <unistd.h>
#include <string.h>

#include "log.h"
#include "mem.h"
#include "core_conf.h"
#include "core_data.h"
#include "packet_ops.h"
#include "protocols.h"

void *_get_packet(unsigned int protocol, char *maker, unsigned long *packet_id, struct core_data **_core_data)
{
	log_info("begin get_packet.");

	struct core_data_queue *core_data_queue = core_conf.core_data_queue;
	if (core_data_queue == NULL || packet_id == NULL || _core_data == NULL) {
		log_error("core_data_queue is NULL, _core_data is NULL or packet_id is NULL.");	
		return NULL;
	}

	/* 
	 * I want to watch Fairy Tail now... so I locked all locks... 
	 * If you don't want this platform performed like a tortoise, 
	 * just do the most important things here. 
	 * */

	int count = 0;
	int add_dont_free = 0;

	pthread_mutex_lock(&(core_data_queue->lock));
	if (*_core_data == NULL || (*_core_data)->id == core_data_queue->core_data_tail->id) {
		core_data_queue->dont_free++;
		add_dont_free = 1;
	}
	pthread_mutex_unlock(&(core_data_queue->lock));

do_get_packet:
	if (count >= 2) {
		goto do_fail;
	}

	if (core_data_queue->count == 0) {
		log_info("No packet now.");	
		goto do_sleep;
	}
	
	struct core_data *core_data;
	if (core_data_queue->freed > *packet_id || *_core_data == NULL) {
		core_data = core_data_queue->core_data_tail;
	} else {
		core_data = (*_core_data)->prev;
	}

	int found = 0;
	for ( ; core_data != NULL; core_data = core_data->prev) {
		if (protocol == PROTOCOL_ALL) {
			found = 1;
			break;
		}
		struct packet_tag *packet_tag = core_data->packet_tag_head;
		while (packet_tag != NULL) {
			if (maker == NULL) {
				if (packet_tag->protocol == protocol) {
					found = 1;
					break;
				}		
			} else {
				if (packet_tag->protocol == protocol 
					&& (strcmp(packet_tag->maker, maker) == 0)) {
					found = 1;
					break;
				}
			}

			packet_tag = packet_tag->next;
		}
		if (found == 1) {
			break;
		}
	}

	if (core_data == NULL) {
		goto do_sleep;
	}

	if (found == 1) {
		pthread_mutex_lock(&(core_data->lock));
		core_data->ref++;
		pthread_mutex_unlock(&(core_data->lock));

		*packet_id = core_data->id;
		*_core_data = core_data;

		if (add_dont_free == 1) {
			pthread_mutex_lock(&(core_data_queue->lock));
			core_data_queue->dont_free--;
			pthread_mutex_unlock(&(core_data_queue->lock));
		}

		return core_data->data;
	}

do_sleep:
	count++;
	//sleep(SLEEP_TIME);
	usleep(USLEEP_TIME);
	goto do_get_packet;

do_fail:
	if (add_dont_free == 1) {
		pthread_mutex_lock(&(core_data_queue->lock));
		core_data_queue->dont_free--;
		pthread_mutex_unlock(&(core_data_queue->lock));
	}
	usleep(USLEEP_TIME);

	return NULL;
}

void _free_packet(struct core_data *_core_data)
{
	log_info("begin free_packet.");
	if (_core_data == NULL) {
		return;
	}

	pthread_mutex_lock(&(_core_data->lock));
	_core_data->ref--;
	pthread_mutex_unlock(&(_core_data->lock));
}

struct core_data *_get_core_data(struct core_data *_core_data)
{
	log_info("begin get_core_data.");
	return _core_data;
}

static void do_make_tag(struct core_data *core_data, struct packet_tag *packet_tag)
{
	log_info("begin do_make_tag.");
	if (core_data == NULL || packet_tag == NULL) {
		log_warning("core_data is NULL os packet_tag is NULL.");
		return;
	}

	pthread_mutex_lock(&(core_data->lock));

	struct packet_tag *pt = NULL;
	if (core_data->packet_tag_head == NULL) {
		core_data->packet_tag_head = packet_tag;
	} else {
		pt = core_data->packet_tag_head;
		for (; pt->next != NULL; pt = pt->next) {
			/* waitting... */
		}
		pt->next = packet_tag;
	}

	pthread_mutex_unlock(&(core_data->lock));
}

void _make_tag(int protocol, char *name, struct core_data *_core_data)
{
	log_info("begin make_tag.");
	if (_core_data == NULL) {
		log_warning("_core_data is NULL.");
		return;
	}

	struct packet_tag *packet_tag = (struct packet_tag *)s_malloc(sizeof(struct packet_tag));
	if (packet_tag == NULL) {
		log_error("No enough mem.");
		return;
	}

	packet_tag->protocol = protocol;
	if (name != NULL) {
		strcpy(packet_tag->maker, name);
	} else {
		strcpy(packet_tag->maker, "no name");
	}

	packet_tag->next = NULL;

	do_make_tag(_core_data, packet_tag);
}
